If you are using WPF Diagrams to represent a business object graph then you will typically store the business objects directly, for example in a database. However if you need a way to save and load diagrams without regard to any underlying business objects, WPF Diagrams provides a quick way to do so using XML.

Serializing Diagrams as XML

To save or load a diagram using XML, use the DiagramXmlSerializer class. The two main methods on this class are:

CopySaving a diagram to a file
new DiagramXmlSerializer().Serialize(diagram).Save(fileName);
CopyLoading a diagram from a file
ds.Diagram = new DiagramXmlSerializer().Deserialize(XDocument.Load(fileName));

Handling Custom Shapes and Line Types

The DiagramXmlSerializer only knows about the shapes and line types built into WPF Diagrams. If you have defined your own shapes or line types then it will serialize them using the Name property, but for deserialization you will need to provide resolver methods to map these names to your own shapes or line types. A resolver method must have a single string, and a return type of DiagramShape or DiagramLineType as appropriate.

CopyResolver methods for user-defined shapes and line types
var serializer = new DiagramXmlSerializer
{
  ShapeNameResolver = UserShapes.TryResolveShapeName,
  LineTypeNameResolver = UserShapes.TryResolveLineTypeName,
};

public static class UserShapes
{
  public static DiagramShape TryResolveShapeName(string name)
  {
    if (name == "WShape")
      return UserShapes.WShape;
    return null;
  }
}

A resolver method should return null to indicate that it didn’t recognise the name that was passed to it.
 

Serializing Custom Element Types

To serialize custom node or connection types, subclass DiagramXmlSerializer and override the NodeElementCore and ConnectionElementCore methods. You can call the base class methods for nodes and connections that are not of your custom types.

CopySerializing custom node types
private static readonly XName MyNodeElementName = "MyNode";

protected override XElement NodeElementCore(DiagramNode node)
{
  if (node is MyNode)
  {
    return new XElement(MyNodeElementName);
  }
  return base.NodeElementCore(node);
}

You don’t need to explicitly serialize the Bounds, Rotation, ZOrder or textual Data of a node. These are serialized automatically.
 

You don’t need to explicitly serialize the connection points, LineType, Segments, ZOrder or textual Data of a connection. These are serialized automatically.
 

CopyDeserializing custom node types
protected override DiagramNode CreateNode(XElement xml)
{
  if (xml.Name == MyNodeElementName)
  {
    return new MyNode();
  }
  return base.CreateNode(xml);
}

Again, you don’t need to explicitly deserialize the core attributes listed above. These are deserialized automatically.
 

Serializing Custom Data

The DiagramXmlSerializer assumes that the Data property for nodes and connections contains text. If your node or connection’s Data value is not text, then you can override the way it is serialized and deserialized by setting the OnSerializeCustomNodeData, OnSerializeCustomConnectionData, OnDeserializeCustomNodeData and OnDeserializeCustomConnectionData callbacks.

The Serialize callbacks receive an XElement and a node or connection object. Your callback should examine the node or connection object data and, if it determines that the data requires custom serialization, it should add the necessary attributes or child elements to the XElement, and return true. If the data does not require custom serialization, the callback should return false, indicating that it did not process the data and the default (text) processing should be applied.

The Deserialize callbacks receive an XElement. Your callback should return the desired value of the node or connection Data property, or null to indicate that it did not process the element and the default (text) processing should be applied.

This means you can’t return null from a Deserialize callback. Instead, return an appropriate ‘empty’ value for your custom data type.
 

CopySerializing and deserializing a non-string Data property
private static bool SerializeData(XElement element, DiagramNode node)
{
  List<string> strings = node.Data as List<string>;
  if (strings != null)
  {
    element.Add(new XElement("Values", strings.Select(s =>
      new XElement("Value",
        new XAttribute("Text", s)))));
    return true;
  }
  return false;  // default serialization
}

private static object DeserializeData(XElement element)
{
  XElement values = element.Element("Values");
  if (values != null)
  {
    return values.Elements("Value")
                 .Select(e => (string)(e.Attribute("Text")))
                 .ToList();
  }
  return null;  // default deserialization
}

// Usage:
DiagramXmlSerializer serializer = new DiagramXmlSerializer
{
  OnSerializeCustomNodeData = SerializeData,
  OnDeserializeCustomNodeData = DeserializeData
};